package com.tools.web.work.excel.poi;

import com.tools.common.object.Note;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;

import java.util.*;

/**
 * 导出 Excel 时 Sheet 页的结构对象
 * */
@Note("导出 Excel 时 Sheet 页的结构对象")
public class SheetExporter {

    @Note("sheet 页的名称")
    private String sheetName;

    @Note("表头行的高度")
    private int headHeight = 70;

    @Note("除了表头行外，每一行的高度")
    private int rowHeight = 40;

    @Note("表头的样式")
    private CellStyle headStyle;

    @Note("表单元格的样式")
    private CellStyle cellStyle;

    @Note("表头配置类")
    private ExcelHead excelHead;

    @Note("是否合并所有相邻的相同值的单元格")
    private boolean sameMergeOfAllField;

    @Note("合并单元格索引记录容器")
    private Map<Object, List<MergeCell>> mergeCellIndexMap;

    @Note("忽略需要导出字段")
    private Set<String> ignoreFieldSet;

    @Note("导出写入 - 行数据处理器接口")
    private RowWriteHandler<?> rowWriteHandler;

    /* ************************************************************************************************************
     *
     *      构造器
     *
     * ************************************************************************************************************
     * */

    public SheetExporter(ExcelHead excelHead) {
        this.setExcelHead(excelHead);
    }

    public SheetExporter(ExcelHead excelHead, RowWriteHandler<?> rowWriteHandler) {
        this.setExcelHead(excelHead);

    }

    public static SheetExporter of(ExcelHead excelHead) {
        return new SheetExporter(excelHead);
    }

    /* ************************************************************************************************************
     *
     *      Getter / Setter
     *
     * ************************************************************************************************************
     * */

    public String getSheetName() {
        return sheetName;
    }

    public void setSheetName(String sheetName) {
        this.sheetName = sheetName;
    }

    public int getHeadHeight() {
        return headHeight;
    }

    public void setHeadHeight(int headHeight) {
        this.headHeight = (headHeight > 0) ? headHeight : 40;
    }

    public int getRowHeight() {
        return rowHeight;
    }

    public void setRowHeight(int rowHeight) {
        this.rowHeight = (rowHeight > 0) ? rowHeight : 25;
    }

    public CellStyle getHeadStyle() {
        return headStyle;
    }

    public void setHeadStyle(CellStyle headStyle) {
        this.headStyle = headStyle;
    }

    public CellStyle getCellStyle() {
        return cellStyle;
    }

    public void setCellStyle(CellStyle cellStyle) {
        this.cellStyle = cellStyle;
    }

    public ExcelHead getExcelHead() {
        return excelHead;
    }

    public void setExcelHead(ExcelHead excelHead) {
        if(excelHead == null) throw new NullPointerException("Excel 表头配置为 null");
        this.excelHead = excelHead;
    }

    public Map<Object, List<MergeCell>> getMergeCellIndexMap() {
        return mergeCellIndexMap;
    }

    public void setMergeCellIndexMap(Map<Object, List<MergeCell>> mergeCellIndexMap) {
        this.mergeCellIndexMap = mergeCellIndexMap;
    }

    public boolean isSameMergeOfAllField() {
        return sameMergeOfAllField;
    }

    public void setSameMergeOfAllField(boolean sameMergeOfAllField) {
        this.sameMergeOfAllField = sameMergeOfAllField;
    }

    Set<String> getIgnoreFieldSet() {
        return ignoreFieldSet;
    }

    public RowWriteHandler<?> getRowWriteHandler() {
        return rowWriteHandler;
    }

    public <T> RowWriteHandler<T> getRowWriteHandler(Class<T> valueType) {
        return (RowWriteHandler<T>) rowWriteHandler;
    }

    public void setRowWriteHandler(RowWriteHandler<?> rowWriteHandler) {
        this.rowWriteHandler = rowWriteHandler;
    }

    @Override
    public String toString() {
        return "SheetExporter{" +
                "sheetName=" + sheetName +
                ", headHeight=" + headHeight +
                ", rowHeight=" + rowHeight +
                ", excelHead=" + excelHead +
                ", headStyle=" + headStyle +
                ", cellStyle=" + cellStyle +
                ", sameMergeOfAllField=" + sameMergeOfAllField +
                ", rowWriteHandler=" + rowWriteHandler +
                ", mergeCellIndexMap=" + mergeCellIndexMap +
                ", ignoreFieldSet=" + ignoreFieldSet +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SheetExporter sheetExporter = (SheetExporter) o;
        return Objects.equals(excelHead, sheetExporter.excelHead);
    }

    @Override
    public int hashCode() {
        return Objects.hash(excelHead);
    }

    public int getMaxHeadRowNum() {
        return excelHead.getMaxHeadRowNum();
    }

    public short calcRowHeight() {
        return (short) (rowHeight * 20);
    }

    public short calcHeadHeight() {
        return (short) (headHeight * 20);
    }

    /* ************************************************************************************************************
     *
     *      链式调用
     *
     * ************************************************************************************************************
     * */

    public SheetExporter sheetName(String sheetName) {
        this.setSheetName(sheetName);
        return this;
    }

    public SheetExporter headHeight(int headHeight) {
        this.setHeadHeight(headHeight);
        return this;
    }

    public SheetExporter rowHeight(int rowHeight) {
        this.setRowHeight(rowHeight);
        return this;
    }

    public SheetExporter headStyle(CellStyle headStyle) {
        this.setHeadStyle(headStyle);
        return this;
    }

    public SheetExporter cellStyle(CellStyle cellStyle) {
        this.setCellStyle(cellStyle);
        return this;
    }

    public SheetExporter excelHead(ExcelHead excelHead) {
        this.setExcelHead(excelHead);
        return this;
    }

    public SheetExporter mergeCellIndexMap(Map<Object, List<MergeCell>> mergeCellIndexMap) {
        this.setMergeCellIndexMap(mergeCellIndexMap);
        return this;
    }

    public SheetExporter sameMergeOfAllField(boolean sameMergeOfAllField) {
        this.setSameMergeOfAllField(sameMergeOfAllField);
        return this;
    }

    public SheetExporter rowWriteHandler(RowWriteHandler<?> rowWriteHandler) {
        this.setRowWriteHandler(rowWriteHandler);
        return this;
    }

    /* ************************************************************************************************************
     *
     *      自定义方法
     *
     * ************************************************************************************************************
     * */

    public SheetExporter igField(String field) {
        this.private_lazyLoadIgnoreFieldSet();
        this.ignoreFieldSet.add(field);
        return this;
    }

    public SheetExporter igFields(String... fields) {
        if(fields == null || fields.length == 0) return this;
        this.private_lazyLoadIgnoreFieldSet();
        Collections.addAll(this.ignoreFieldSet, fields);
        return this;
    }

    public SheetExporter igFields(Iterable<String> fields) {
        if(fields == null) return this;
        this.private_lazyLoadIgnoreFieldSet();
        for(String field : fields) this.ignoreFieldSet.add(field);
        return this;
    }

    public SheetExporter delIgField(String field) {
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return this;
        this.ignoreFieldSet.remove(field);
        return this;
    }

    public SheetExporter delIgFields(String... fields) {
        if(fields == null || fields.length == 0) return this;
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return this;
        for(String field : fields) this.ignoreFieldSet.remove(field);
        return this;
    }

    public SheetExporter delIgFields(Iterable<String> fields) {
        if(fields == null) return this;
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return this;
        for(String field : fields) this.ignoreFieldSet.remove(field);
        return this;
    }

    public SheetExporter clearIgnoreFieldSet() {
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return this;
        this.ignoreFieldSet.clear();
        return this;
    }

    public boolean hasIgField(String field) {
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return false;
        return this.ignoreFieldSet.contains(field);
    }

    public boolean hasIgAllField(String... fields) {
        if(fields == null || fields.length == 0) return false;
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return false;
        for(String field : fields) if(!this.ignoreFieldSet.contains(field)) return false;
        return true;
    }

    public boolean hasIgAllField(Iterable<String> fields) {
        if(fields == null) return false;
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return false;
        for(String field : fields) if(!this.ignoreFieldSet.contains(field)) return false;
        return true;
    }

    public boolean hasIgAnyField(String... fields) {
        if(fields == null || fields.length == 0) return false;
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return false;
        for(String field : fields) if(this.ignoreFieldSet.contains(field)) return true;
        return false;
    }

    public boolean hasIgAnyField(Iterable<String> fields) {
        if(fields == null) return false;
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return false;
        for(String field : fields) if(this.ignoreFieldSet.contains(field)) return true;
        return false;
    }

    public boolean ignoreFieldSetIsEmpty() {
        return this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty();
    }

    public int ignoreFieldCount() {
        return this.ignoreFieldSet == null ? 0 : this.ignoreFieldSet.size();
    }

    public Set<String> copyIgnoreFieldSet() {
        if(this.ignoreFieldSet == null || this.ignoreFieldSet.isEmpty()) return new HashSet<>();
        return new HashSet<>(this.ignoreFieldSet);
    }


    public Cell createCell(Row row, int columnIndex, Sheet sheet, CellStyle cellStyle) {
        Cell cell = row.createCell(columnIndex);
        cell.setCellStyle(cellStyle);
        ExcelField excelField = this.excelHead.get(columnIndex);
        if(excelField == null) {
            throw new IllegalArgumentException(columnIndex + "列索引错误，不能找到相关的列配置");
        }
        sheet.setColumnWidth(columnIndex, excelField.getHeadWidth() * 256);
        return cell;
    }

    public Object addMergeCell(Row row, int columnIndex, Object value) {
        return this.addMergeCell(row.getRowNum(), columnIndex, value);
    }

    public Object addMergeCell(Row row, Cell cell, Object value) {
        return this.addMergeCell(row.getRowNum(), cell.getColumnIndex(), value);
    }

    public Object addMergeCell(int rowIndex, Cell cell, Object value) {
        return this.addMergeCell(rowIndex, cell.getColumnIndex(), value);
    }
    
    public Object addMergeCell(int rowIndex, int columnIndex, Object value) {
        ExcelField excelField = this.excelHead.get(columnIndex);
        if(excelField == null) {
            throw new IllegalArgumentException(columnIndex + " 列索引出错，不能找到相关的列配置");
        }
        if(excelField.isSameValueMerge() || this.sameMergeOfAllField) {
            ExcelKit.recordMergeRowIndex(this.mergeCellIndexMap, rowIndex, columnIndex,
                    excelField.getField() + '-', value);
        }
        return value;
    }
 
    /* ************************************************************************************************************
     *
     *      私有逻辑
     *
     * ************************************************************************************************************
     * */

    @Note("懒加载忽略字段集合")
    private void private_lazyLoadIgnoreFieldSet() {
        if(this.ignoreFieldSet != null) return;
        synchronized (this) {
            if(this.ignoreFieldSet != null) return;
            this.ignoreFieldSet = new HashSet<>();
        }
    }
}

